﻿/// <reference path="../../cannon/cannon.d.ts" />
/// <reference path="../../lib/egret3d.d.ts" />

class Car
{
    _canvasEgret: egret3d.Egret3DCanvas;
    _view3D: egret3d.View3D;
    _cameraCtrl: egret3d.LookAtController;

    boxMeshes: egret3d.Mesh[] = [];
    boxes: CANNON.Body[] = [];

    start()
    {
        this.initCannon();

        this._canvasEgret = new egret3d.Egret3DCanvas();
        this._canvasEgret.x = 0;
        this._canvasEgret.y = 0;
        this._canvasEgret.width = window.innerWidth;
        this._canvasEgret.height = window.innerHeight;
        this._canvasEgret.start();

        var lightGroup = new egret3d.LightGroup();
        var light = new egret3d.DirectLight(new egret3d.Vector3D(1, 1, -1));
        light.diffuse = 0xffffff;
        light.intensity = 1;
        lightGroup.addLight(light);

        this._view3D = new egret3d.View3D(0, 0, window.innerWidth, window.innerHeight);
        //this._view3D.camera3D.lookAt(new egret3d.Vector3D(-20, -5, 8), new egret3d.Vector3D(0, 0, 0), new egret3d.Vector3D(0, 0, 1));
        this._view3D.backColor = 0xff333333;
        this._canvasEgret.addView3D(this._view3D);

        //this._cameraCtrl = new egret3d.LookAtController(this._view3D.camera3D, new egret3d.Object3D());
        //this._cameraCtrl.distance = 24;
        //this._cameraCtrl.rotationX = 20;

        this.carMesh = new egret3d.Mesh(new egret3d.CubeGeometry(4, 2, 1), new egret3d.ColorMaterial());
        this.carMesh.material.castShadow = true;
        this.carMesh.material.lightGroup = lightGroup;
        this._view3D.addChild3D(this.carMesh);

        this.wheelMeshes = [];
        for (var i = 0; i < this.vehicle.wheelInfos.length; i++)
        {
            var wheel = this.vehicle.wheelInfos[i];
            var cylinderShape = new egret3d.CylinderGeometry(wheel.radius / 2, wheel.radius);
            var mesh = new egret3d.Mesh(cylinderShape, new egret3d.ColorMaterial());
            mesh.material.castShadow = true;
            mesh.material.lightGroup = lightGroup;
            this._view3D.addChild3D(mesh);
            this.wheelMeshes.push(mesh);
        }

        var plane = new egret3d.Mesh(new egret3d.PlaneGeometry(500, 500), new egret3d.ColorMaterial());
        plane.material.diffuseColor = 0xaaaaaa;
        plane.material.acceptShadow = true;
        plane.material.lightGroup = lightGroup;
        plane.rotationX = 90;
        this._view3D.addChild3D(plane);

        var cylinder = new egret3d.Mesh(new egret3d.CylinderGeometry(10, 24), new egret3d.ColorMaterial());
        cylinder.material.diffuseColor = 0xaaaaaa;
        cylinder.material.acceptShadow = true;
        cylinder.material.lightGroup = lightGroup;
        //cylinder.position = new egret3d.Vector3D(10, 20, -20);
        //cylinder.rotationZ = 60;
        cylinder.position = new egret3d.Vector3D(this.cylinderBody.position.x, this.cylinderBody.position.y, this.cylinderBody.position.z);
        cylinder.orientation = new egret3d.Quaternion(this.cylinderBody.quaternion.x, this.cylinderBody.quaternion.y, this.cylinderBody.quaternion.z, this.cylinderBody.quaternion.w);
        this._view3D.addChild3D(cylinder);

        this._canvasEgret.addEventListener(egret3d.Event3D.ENTER_FRAME, this.update, this);
        egret3d.Input.addEventListener(egret3d.Event3D.RESIZE, this.onResize, this);

        document.onkeydown = (e) => { this.handler(e); };
        document.onkeyup = (e) => { this.handler(e); };

        document.onmousedown = (e) => { this.mouseDown(e); };
        document.onmousemove = (e) => { this.mouseMove(e); };
        document.onmouseup = (e) => { this.mouseUp(e); };

        document.ontouchstart = (e) => { this.touchStart(e) };
        document.ontouchmove = (e) => { this.touchMove(e) };
        document.ontouchend = (e) => { this.touchEnd(e) };
    }

    world: CANNON.World;
    mass: number = 300;
    vehicle: CANNON.RaycastVehicle;
    carMesh: egret3d.Mesh;
    wheelBodies: CANNON.Body[];
    wheelMeshes: egret3d.Mesh[];
    cylinderBody: CANNON.Body;
    initCannon()
    {
        this.world = new CANNON.World();
        this.world.quatNormalizeSkip = 2;
        this.world.quatNormalizeFast = true;

        this.world.gravity.set(0, 0, -10);
        this.world.broadphase = new CANNON.SAPBroadphase(this.world);
        this.world.defaultContactMaterial.friction = 0;

        var groundMaterial = new CANNON.Material("groundMaterial");
        var wheelMaterial = new CANNON.Material("wheelMaterial");
        var wheelGroundContactMaterial = window["wheelGroundContractMaterial"] = new CANNON.ContactMaterial(wheelMaterial, groundMaterial, {
            friction: 0.3,
            restitution: 0,
            contactEquationStiffness: 1000
        });
        this.world.addContactMaterial(wheelGroundContactMaterial);

        var chassisShape;
        chassisShape = new CANNON.Box(new CANNON.Vec3(2, 1, 0.5));
        var chassisBody = new CANNON.Body({ mass: this.mass });
        chassisBody.addShape(chassisShape);
        chassisBody.position.set(0, 0, 4);
        chassisBody.angularVelocity.set(0, 0, 0.5);

        var options = {
            radius: 0.5,
            directionLocal: new CANNON.Vec3(0, 0, -1),
            suspensionStiffness: 30,
            suspensionRestLength: 0.3,
            frictionSlip: 5,
            dampingRelaxation: 2.3,
            dampingCompression: 4.4,
            maxSuspensionForce: 100000,
            rollInfluence: 0.01,
            axleLocal: new CANNON.Vec3(0, 1, 0),
            chassisConnectionPointLocal: new CANNON.Vec3(1, 1, 0),
            maxSuspensionTravel: 0.3,
            customSlidingRotationalSpeed: -30,
            useCustomSlidingRotationalSpeed: true
        };
        
        this.vehicle = new CANNON.RaycastVehicle({ chassisBody: chassisBody });

        options.chassisConnectionPointLocal.set(1, 1, 0);
        this.vehicle.addWheel(options);

        options.chassisConnectionPointLocal.set(1, -1, 0);
        this.vehicle.addWheel(options);

        options.chassisConnectionPointLocal.set(-1, 1, 0);
        this.vehicle.addWheel(options);

        options.chassisConnectionPointLocal.set(-1, -1, 0);
        this.vehicle.addWheel(options);

        this.vehicle.addToWorld(this.world);

        this.wheelBodies = [];
        for (var i = 0; i < this.vehicle.wheelInfos.length; i++)
        {
            var wheel = this.vehicle.wheelInfos[i];
            var cylinderShape = new CANNON.Cylinder(wheel.radius, wheel.radius, wheel.radius / 2, 20);
            var wheelBody = new CANNON.Body({ mass: 0 });
            wheelBody.type = CANNON.Body.KINEMATIC;
            wheelBody.collisionFilterGroup = 0;
            wheelBody.addShape(cylinderShape);
            wheelBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), Math.PI / 2);
            this.wheelBodies.push(wheelBody);
            this.world.addBody(wheelBody);
        }

        var cylinderShape = new CANNON.Cylinder(24, 24, 10, 20);
        this.cylinderBody = new CANNON.Body({ mass: 0 });
        var q = new CANNON.Quaternion();
        q.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), Math.PI / 2);
        this.cylinderBody.addShape(cylinderShape, new CANNON.Vec3(), q);
        this.cylinderBody.position.set(20, 10, -20);
        this.world.addBody(this.cylinderBody);

        var groundShape = new CANNON.Plane();
        var groundBody = new CANNON.Body({ mass: 0 });
        groundBody.addShape(groundShape);
        //groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
        this.world.addBody(groundBody);
    }

    maxSteerVal = 0.5;
    maxForce = 1000;
    brakeForce = 1000000;
    keyup: boolean = true;
    handler(event: KeyboardEvent)
    {
        this.keyup = (event.type == 'keyup');
        if (!this.keyup && event.type !== 'keydown')
        {
            return;
        }

        this.vehicle.setBrake(0, 0);
        this.vehicle.setBrake(0, 1);
        this.vehicle.setBrake(0, 2);
        this.vehicle.setBrake(0, 3);

        switch (event.keyCode)
        {

            case 38: // forward
                this.vehicle.applyEngineForce(this.keyup ? 0 : -this.maxForce, 2);
                this.vehicle.applyEngineForce(this.keyup ? 0 : -this.maxForce, 3);
                break;

            case 40: // backward
                this.vehicle.applyEngineForce(this.keyup ? 0 : this.maxForce, 2);
                this.vehicle.applyEngineForce(this.keyup ? 0 : this.maxForce, 3);
                break;

            case 32: // spacebar
                this.vehicle.setBrake(this.brakeForce, 0);
                this.vehicle.setBrake(this.brakeForce, 1);
                this.vehicle.setBrake(this.brakeForce, 2);
                this.vehicle.setBrake(this.brakeForce, 3);
                break;

            case 39: // right
                this.vehicle.setSteeringValue(this.keyup ? 0 : this.maxSteerVal, 0);
                this.vehicle.setSteeringValue(this.keyup ? 0 : this.maxSteerVal, 1);
                break;

            case 37: // left
                this.vehicle.setSteeringValue(this.keyup ? 0 : -this.maxSteerVal, 0);
                this.vehicle.setSteeringValue(this.keyup ? 0 : -this.maxSteerVal, 1);
                break;

        }
    }

    mousePress: boolean = false;
    lastPoint: CANNON.Vec3;
    direction: CANNON.Vec3;
    mouseDown(e: MouseEvent)
    {
        this.mousePress = true;
        this.lastPoint = new CANNON.Vec3(e.x, 0, -e.y);
    }

    mouseMove(e: MouseEvent)
    {
        if (this.mousePress)
        {
            var vec = new CANNON.Vec3(e.x, 0, -e.y);
            this.direction = vec.vsub(this.lastPoint);
            this.direction.normalize();
        }
    }

    mouseUp(e: MouseEvent)
    {
        this.mousePress = false;
    }

    touchStart(e: TouchEvent)
    {
        this.mousePress = true;
        this.lastPoint = new CANNON.Vec3(e.touches[0].clientX, 0, -e.touches[0].clientY);
    }

    touchMove(e: TouchEvent)
    {
        if (this.mousePress)
        {
            var vec = new CANNON.Vec3(e.touches[0].clientX, 0, -e.touches[0].clientY);
            this.direction = vec.vsub(this.lastPoint);
            this.direction.normalize();
        }
    }

    touchEnd(e: TouchEvent)
    {
        this.mousePress = false;
    }

    fps: number = 60;
    time: number = Date.now();
    updatePhysics()
    {
        this.fps = 1000 / (Date.now() - this.time);
        this.time = Date.now();

        this.world.step(1 / this.fps);

        for (var i = 0; i < this.vehicle.wheelInfos.length; i++)
        {
            this.vehicle.updateWheelTransform(i);
            var t = (this.vehicle.wheelInfos[i] as CANNON.WheelInfo).worldTransform;
            var wheelBody = this.wheelBodies[i];
            wheelBody.position.copy(t.position);
            wheelBody.quaternion.copy(t.quaternion);

            this.wheelMeshes[i].position = new egret3d.Vector3D(t.position.x, t.position.y, t.position.z);
            this.wheelMeshes[i].orientation = new egret3d.Quaternion(t.quaternion.x, t.quaternion.y, t.quaternion.z, t.quaternion.w);
        }

        this.carMesh.position = new egret3d.Vector3D(this.vehicle.chassisBody.position.x, this.vehicle.chassisBody.position.y, this.vehicle.chassisBody.position.z);
        this.carMesh.orientation = new egret3d.Quaternion(this.vehicle.chassisBody.quaternion.x, this.vehicle.chassisBody.quaternion.y, this.vehicle.chassisBody.quaternion.z, this.vehicle.chassisBody.quaternion.w);
    }

    update()
    {
        //this._cameraCtrl.update();

        this.updatePhysics();

        if (this.mousePress)
        {
            this.vehicle.setBrake(0, 0);
            this.vehicle.setBrake(0, 1);
            this.vehicle.setBrake(0, 2);
            this.vehicle.setBrake(0, 3);

            var asinZ = Math.asin(this.direction.z);
            var acosX = Math.acos(this.direction.x);
            if (asinZ > Math.PI / 6)//up
            {
                this.vehicle.applyEngineForce(-this.maxForce, 2);
                this.vehicle.applyEngineForce(-this.maxForce, 3);
            }
            if (asinZ < -Math.PI / 6)//down
            {
                this.vehicle.applyEngineForce(this.maxForce, 2);
                this.vehicle.applyEngineForce(this.maxForce, 3);
            }
            if (acosX > Math.PI * 2 / 3)//left
            {
                this.vehicle.setSteeringValue(-this.maxSteerVal, 0);
                this.vehicle.setSteeringValue(-this.maxSteerVal, 1);
            }
            if (acosX < Math.PI / 3)//right
            {
                this.vehicle.setSteeringValue(this.maxSteerVal, 0);
                this.vehicle.setSteeringValue(this.maxSteerVal, 1);
            }
        }
        else if (this.keyup)
        {
            this.vehicle.setSteeringValue(0, 0);
            this.vehicle.setSteeringValue(0, 1);
            this.vehicle.applyEngineForce(0, 2);
            this.vehicle.applyEngineForce(0, 3);
        }

        this._view3D.camera3D.lookAt(new egret3d.Vector3D(-20, -5, 8), this.carMesh.position, new egret3d.Vector3D(0, 0, 1));
    }

    onResize(e: egret3d.Event3D)
    {
        this._view3D.width = this._canvasEgret.width;
        this._view3D.height = this._canvasEgret.height;
    }
}

window.onload = () =>
{
    var a = new Car();
    a.start();
};
